package org.parngon.maze;

import java.awt.Point;
import java.awt.Dimension;

import java.util.Vector;
import java.util.Random;

import org.parngon.common.Coord;

public class Tile<T extends Coord>
{
  public static final int TOP = 1;
  public static final int BOTTOM = 2;
  public static final int LEFT = 4;
  public static final int RIGHT = 8;
  public static final int CARVED = 16;
  public static final int BRICK = 32;

  public static final int MARK1 = 16384;
  public static final int MARK2 = 16384 << 1;
  public static final int MARK3 = 16384 << 2;
  public static final int MARK4 = 16384 << 3;

  //public int x;
  //public int y;
  T location;
  int dim = 0;

  public Tile<T> top = null;
  public Tile<T> bottom = null;
  public Tile<T> left = null;
  public Tile<T> right = null;

  int data = 0;

  Random rand = null;

  public Tile(T l, Maze<T> m)
  {
    this.location = l;
    this.map = m;
    this.dim = l.getDimension();

    rand = new Random(m.getSeed());
//    brick();
  }
/*
  public Tile(int x, int y, Maze m)
  {
    this.x = x;
    this.y = y;
    this.map = m;

    rand = new Random(m.getSeed());
//    brick();
  }
*/
  public void carve()
  {
    if ( isCarved() ) System.out.println(this + " double carved");

    data |= CARVED;
    data &= ~BRICK;
  }

  public boolean isCarved()
  {
    return (data & CARVED) == CARVED;
  }

  public void brick()
  {
    data |= BRICK;
//    data &= ~CARVED;
    topWall(true);
    bottomWall(true);
    leftWall(true);
    rightWall(true);
  }

  public boolean isBrick()
  {
    return (data & BRICK) == BRICK;
  }

  public void mark(int m)
  {
    switch ( m ) {
      case 1:
        data |= MARK1;
        break;
      case 2:
        data |= MARK2;
        break;
      case 3:
        data |= MARK3;
        break;
      case 4:
        data |= MARK4;
        break;
      default:
        data &= ~MARK1;
        data &= ~MARK2;
        data &= ~MARK3;
        data &= ~MARK4;
    }
  }

  public void mark(boolean m)
  {
    if ( m ) {
      data |= MARK1 | MARK2 | MARK3 | MARK4;
    } else {
      data &= ~MARK1;
      data &= ~MARK2;
      data &= ~MARK3;
      data &= ~MARK4;
    }
  }

  public int getMark()
  {
    if ( (data & MARK1) == MARK1 ) return 1;
    if ( (data & MARK2) == MARK2 ) return 2;
    if ( (data & MARK3) == MARK3 ) return 3;
    if ( (data & MARK4) == MARK4 ) return 4;

    return -1;
  }

  public boolean isMarked()
  {
    return getMark() > 0;
  }

  public void _topWall(boolean t)
  {
    if ( t ) {
      data |= TOP;
    } else {
      data &= ~TOP;
    }
  }

  public void topWall(boolean t)
  {
    _topWall(t);

    if ( top != null ) top._bottomWall(t);
  }

  public void _bottomWall(boolean b)
  {
    if ( b ) {
      data |= BOTTOM;
    } else {
      data &= ~BOTTOM;
    }
  }

  public void bottomWall(boolean b)
  {
    _bottomWall(b);

    if ( bottom != null ) bottom._topWall(b);
  }

  public void _leftWall(boolean l)
  {
    if ( l ) {
      data |= LEFT;
    } else {
      data &= ~LEFT;
    }
  }

  public void leftWall(boolean l)
  {
    _leftWall(l);

    if ( left != null ) left._rightWall(l);
  }

  public void _rightWall(boolean r)
  {
    if ( r ) {
      data |= RIGHT;
    } else {
      data &= ~RIGHT;
    }
  }

  public void rightWall(boolean r)
  {
    _rightWall(r);

    if ( right != null ) right._leftWall(r);
  }

  public int getNumberOfUncarvedNeighbours()
  {
    int result = 0;

    if ( top != null && !top.isCarved() ) result++;
    if ( bottom != null && !bottom.isCarved() ) result++;
    if ( left != null && !left.isCarved() ) result++;
    if ( right != null && !right.isCarved() ) result++;

    return result;
  }

  public int getNumberOfNeighbours()
  {
    int result = 0;

    if ( top != null && !hasTopWall() ) result++;
    if ( bottom != null && !hasBottomWall() ) result++;
    if ( left != null && !hasLeftWall() ) result++;
    if ( right != null && !hasRightWall() ) result++;

    return result;
  }

  public boolean hasTopWall()
  {
    return (data & TOP) == TOP;
  }

  public boolean hasBottomWall()
  {
    return (data & BOTTOM) == BOTTOM;
  }

  public boolean hasLeftWall()
  {
    return (data & LEFT) == LEFT;
  }

  public boolean hasRightWall()
  {
    return (data & RIGHT) == RIGHT;
  }

  public boolean isDeadend()
  {
    return isCarved() && !isBrick() &&
      (hasTopWall() && hasBottomWall() && hasLeftWall() && !hasRightWall() ||
      hasTopWall() && hasBottomWall() && hasRightWall() && !hasLeftWall() ||
      hasTopWall() && hasLeftWall() && hasRightWall() && !hasBottomWall() ||
      hasBottomWall() && hasLeftWall() && hasRightWall() && !hasTopWall());
  }

  public Tile<T> getRandomDisconnectedNeighbour()
  {
    Tile<T> result = null;

    Vector<Tile<T>> ns = new Vector<Tile<T>>();

    if ( top != null && hasTopWall() ) ns.add(top);
    if ( bottom != null && hasBottomWall() ) ns.add(bottom);
    if ( left != null && hasLeftWall() ) ns.add(left);
    if ( right != null && hasRightWall() ) ns.add(right);

    if ( ns.size() > 0 ) result = ns.elementAt(rand.nextInt(ns.size()));

    return result;
  }

  public Tile<T> getRandomConnectedNeighbour()
  {
    Tile<T> result = null;

    Vector<Tile<T>> ns = new Vector<Tile<T>>();

    if ( top != null && !hasTopWall() ) ns.add(top);
    if ( bottom != null && !hasBottomWall() ) ns.add(bottom);
    if ( left != null && !hasLeftWall() ) ns.add(left);
    if ( right != null && !hasRightWall() ) ns.add(right);

    if ( ns.size() > 0 ) result = ns.elementAt(rand.nextInt(ns.size()));

    return result;
  }

  public int getNeighbourDirection(Tile<T> n)
  {
    if ( n == null ) return -1;

    if ( n == top ) return TOP;
    if ( n == bottom ) return BOTTOM;
    if ( n == left ) return LEFT;
    if ( n == right ) return RIGHT;

    return -1;
  }

  public boolean equals(Object o)
  {
    if ( o == null ) return false;

    return location.equals(((Tile<?>)o).getLocation());
/*
    Tile oo = (Tile)o;

    return oo.x == this.x && oo.y == this.y;
*/
  }

  public String toString()
  {
    return "(" + location + ")";
  }

  public boolean isNeighbour(Tile<T> t)
  {
    return t == top || t == bottom || t == left || t == right;
  }

  public boolean isConnectedNeighbour(Tile<T> t)
  {
    if ( t == top && !hasTopWall() ) return true;
    if ( t == bottom && !hasBottomWall() ) return true;
    if ( t == left && !hasLeftWall() ) return true;
    if ( t == right && !hasRightWall() ) return true;

    return false;
  }

  public Tile[] getConnectedNeighbours()
  {
    Vector<Tile<T>> ns = new Vector<Tile<T>>();

    if ( top != null && !hasTopWall() ) ns.add(top);
    if ( bottom != null && !hasBottomWall() ) ns.add(bottom);
    if ( left != null && !hasLeftWall() ) ns.add(left);
    if ( right != null && !hasRightWall() ) ns.add(right);

    Tile<T> result[] = new Tile<T>[ns.size()];
    for ( int i = 0; i < ns.size(); i++ ) {
      result[i] = ns.elementAt(i);
    }

    return result;
  }
/*
  public Point getLocation()
  {
    return new Point(x, y);
  }
*/
  public T getLocation()
  {
    return location;
  }

  double _g = 0;
  Tile<T> parent = null;

  public double g()
  {
    return _g;
  }

  public void g(double _g)
  {
    this._g = _g;
  }

  public Tile<T> getParent()
  {
    return parent;
  }

  public void setParent(Tile<T> p)
  {
    parent = p;
  }

  public double f(Tile<T> dst)
  {
    return _g + map.getHeuristicCost(this, dst);
  }

  Maze<T> map = null;
}
